#!/usr/bin/env ruby

# -------------------------------------------------------------------------- #
# Copyright 2002-2016, OpenNebula Project, OpenNebula Systems                #
#                                                                            #
# Licensed under the Apache License, Version 2.0 (the "License"); you may    #
# not use this file except in compliance with the License. You may obtain    #
# a copy of the License at                                                   #
#                                                                            #
# http://www.apache.org/licenses/LICENSE-2.0                                 #
#                                                                            #
# Unless required by applicable law or agreed to in writing, software        #
# distributed under the License is distributed on an "AS IS" BASIS,          #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #
# See the License for the specific language governing permissions and        #
# limitations under the License.                                             #
#--------------------------------------------------------------------------- #

require 'pp'
require 'digest/md5'
require 'fileutils'

###########################################
# Code to test compilation/linkning flags #
###########################################

TestCodeServer="""
#include <xmlrpc-c/base.hpp>
#include <xmlrpc-c/registry.hpp>

int main(int argc, char *argv[])
{
    xmlrpc_c::registry RequestManagerRegistry;
    return 0;
}"""

TestCodeClient=<<-EOT
#include <cstdlib>
#include <string>
#include <iostream>
#include <xmlrpc-c/girerr.hpp>
#include <xmlrpc-c/base.hpp>
#include <xmlrpc-c/client_simple.hpp>
#include <map>

using namespace std;

int
main(int argc, char **) {
    if (argc-1 > 0) {
        cerr << "This program has no arguments" << endl;
        exit(1);
    }
    try {
        xmlrpc_env env;
        string const serverUrl("http://localhost:8080/RPC2");
        string const methodAllocate("one.vmallocate");
        xmlrpc_c::clientSimple myClient;
        xmlrpc_c::value resultSUBMIT;

        xmlrpc_env_init(&env);

        myClient.call(serverUrl, methodAllocate, "ss",
                      &resultSUBMIT,"SESSION-GOLA&4H9109KVFSG",
                      "MEMORY=345 CPU=4 DISK=[FILE=\\"img\\",TYPE=cd]"
                      "DISK=[FILE=\\"../f\\"]");

        xmlrpc_c::value_array resultArray = xmlrpc_c::value_array(resultSUBMIT);
        vector<xmlrpc_c::value> const paramArrayValue(resultArray.vectorValueValue());

        //check posible Errors:
        xmlrpc_c::value firstvalue;
        firstvalue = static_cast<xmlrpc_c::value>(paramArrayValue[0]);
        xmlrpc_c::value_boolean status = static_cast<xmlrpc_c::value_boolean>(firstvalue);

        xmlrpc_c::value secondvalue;
        secondvalue = static_cast<xmlrpc_c::value>(paramArrayValue[1]);
        xmlrpc_c::value_string valueS = static_cast<xmlrpc_c::value_string>(secondvalue);

        if(static_cast<bool>(status)) {
            //Success, returns the id assigned to the VM:
            cout << "vmid returned: " << static_cast<string>(valueS) << endl;
            return 0;
        }
        else{ //Failure:
            string error_value=static_cast<string>(valueS);
            if (error_value.find("Error inserting",0)!=string::npos ) cout << "Error inserting VM in the database" << endl;
            else if (error_value.find("Error parsing",0)!=string::npos ) cout << "Error parsing VM template" << endl;
            else cout << "Unknown error " << static_cast<string>(valueS) << endl;
        };
    } catch (girerr::error const error) {
        cerr << "Client threw error: " << error.what() << endl;
        //"Client threw error:"
        return 20;
    } catch (std::exception const e) {
        cerr << "Client threw unexpected error." << endl;
        //Unexpected error:
        return 999;
    }
    return 0;
}
EOT

TestCode={
    :client => TestCodeClient,
    :server => TestCodeServer
}

# Executes a command and discards stderr
def exec_command(command)
    lambda {
        #STDERR.puts text
        text=`#{command} 2>/dev/null`
        text.gsub!("\n", " ")
        
        if $?!=0
            STDERR.puts "  Error calling #{command}"
            nil
        else
            text
        end
    }
end

# Adds fixed flags and libraries
def flags_and_libs_array(flags, libs)
    lambda {
        libs_text=libs.collect {|lib| "-l"+lib }.join(' ')
        text=flags+" "+libs_text
        #STDERR.puts text
        text
    }
end

# Array with flags/libs to test
Configs=[
    # Configuration using xmlrpc-c-config
    {
        :description => "xmlrpc-c-config",
        :client => exec_command("xmlrpc-c-config c++2 client --libs --cflags"),
        :server => exec_command("xmlrpc-c-config c++2 abyss-server --libs --cflags")
    },
    # Configuration for fedora
    {
        :description => "pkg-config",
        :client => exec_command("pkg-config xmlrpc_client++ xmlrpc++ --libs"),
        :server => exec_command("pkg-config xmlrpc_server_abyss++ --static --libs")
    },
    # Debian lenny
    {
        :description => "mixed hardcoded libraries and xmlrpc-c-config (debian lenny)",
        :client => flags_and_libs_array("-I/usr/include -L/usr/lib", [
                'xmlrpc_client', 'xmlrpc_client', 'xmlrpc', 'xmlrpc_util',
                'xmlrpc_xmlparse', 'xmlrpc_xmltok', 'xmlrpc_client++', 'xmlrpc++'
            ]),
        :server => exec_command("xmlrpc-c-config c++2 abyss-server --libs --cflags"),
    },
    # Configuration for Mac OS X and Debian
    {
        :description => "hardcoded libraries for Mac OS X (installed using port)",
        :server => flags_and_libs_array("-I/opt/local/include -L/opt/local/lib", [
                'wwwxml', 'xmltok', 'xmlparse', 'wwwzip', 'wwwinit', 'wwwapp',
                'wwwtelnet', 'wwwhtml', 'wwwnews', 'wwwhttp', 'wwwmime', 'wwwgopher',
                'wwwftp', 'wwwfile', 'wwwdir', 'wwwcache', 'wwwstream', 'wwwmux',
                'wwwtrans', 'wwwcore', 'wwwutils', 'md5', 'dl', 'z', 'pthread',
                'xmlrpc_client++', 'xmlrpc_client', 'xmlrpc++', 'xmlrpc',
                'xmlrpc_util', 'xmlrpc_xmlparse', 'xmlrpc_xmltok',
                'xmlrpc_server_abyss++', 'xmlrpc_server++', 'xmlrpc_server_abyss',
                'xmlrpc_server', 'xmlrpc_abyss',
            ]),
        :client => flags_and_libs_array("-I/opt/local/include -L/opt/local/lib", [
                'curl', 'xmlrpc_client++', 'xmlrpc_client', 'xmlrpc++', 'xmlrpc',
                'xmlrpc_util', 'xmlrpc_xmlparse', 'xmlrpc_xmltok', 'wwwxml',
                'xmltok', 'xmlparse', 'wwwzip', 'wwwinit', 'wwwapp', 'wwwtelnet',
                'wwwhtml', 'wwwnews', 'wwwhttp', 'wwwmime', 'wwwgopher', 'wwwftp',
                'wwwfile', 'wwwdir', 'wwwcache', 'wwwstream', 'wwwmux', 'wwwtrans',
                'wwwcore', 'wwwutils', 'm', 'md5'
            ])
    },
    # Example adding more custom libraries
    #    {
    #        :client => exec_command("xmlrpc-c-config client c++ --cflags --libs"),
    #        :server => exec_command("xmlrpc-c-config abyss-server c++ --cflags --libs | tr '\\n' ' ' ; echo -lxmlrpc_server++")
    #    },
]


# Compiles test code with given arguments
def compile(file, args)
    logfile="#{file}.log"
    command="g++ #{file} -o #{file}.out #{ENV['LDFLAGS']} -pthread #{args} 1>>#{logfile} 2>&1"
    
    open(logfile, "a") {|f|
        f.write(command+"\n")
    }
    
    STDERR.puts command
    STDERR.puts ""
    out=system(command)
end

def gen_test_file(kind)
    dir='.xmlrpc_test'
    hash=Digest::MD5.hexdigest(Time.now.to_s+kind.to_s)[0..5]
    fname="xmlrpc_test."+hash+".cc"
    #fname="xmlrpc_test.cc"
    full_path=dir+"/"+fname
    
    FileUtils::mkdir_p(dir)
    f=open(full_path, "w")
    f.write(TestCode[kind])
    f.close
    
    full_path
end

# Tests a configuration and writes build parameters if it is successful
def test_config(kind, config)
    STDERR.puts "Testing recipe: "+config[:description].to_s
    args=config[kind.to_sym].call
    
    return nil if !args
    
    args.strip!
    name=gen_test_file(kind)
    exit_code=compile(name, args)
    #File.delete(name)
    File.delete(name+".out") if File.exists?(name+".out")
    
    return nil if !exit_code
    
    puts args
    
    true
end

# Test each of possible configurations and exits when it finds one working
def search_config(kind)
    Configs.each {|config|
        found=test_config(kind,config)
        exit(0) if found
    }
    exit(1)
end

if !%w{client server}.include?ARGV[0]
    puts "You need to specify client or server"
    exit -1
end

search_config(ARGV[0].to_sym)

